JVM 垃圾回收策略

当 JVM 创建对象遇到内存不足的时候,JVM 会自动触发垃圾回收(Garbage Collection,简称 GC)操作,将那些不再使用但仍然存在内存中的对象当成垃圾清理掉,释放出被占用的内存空间,供新创建的对象使用。

1 对象是否存活

垃圾收集器在进行垃圾回收前得先确定哪些对象可以被回收,哪些对象不能被回收。

1.1 引用计数算法

当对象创建时给对象分配一个引用计数器,当该对象被引用时,计数器值就加 1,当该对象不被引用或者引用失效时,计数器值就减 1;任何时候只要对象的引用计数器值为 0 就认为该对象不再被使用,可以当作垃圾被 GC 处理掉了。

  • 【优点】:实现简单,判定效率高
  • 【缺点】:很难解决对象间循环引用的问题

1.2 可达性分析算法

在主流的 Java 虚拟机中就是通过可达性分析来判定对象是否存活的。

基本思路:通过一系列称为“GC ROOTS” 的对象作为起始点,从这些节点开始向下搜索,到达某个对象所经过的路径称为引用链。如果一个对象和根对象之间有引用链,即根对象到该对象是可达的,说明该对象还处于存活状态,还不能被回收;反之,如果一个对象和根对象之间没有引用链,即根对象到该对象是不可达的,那么这个对象就是可以被回收的。

  • 【优点】:可以找到所有垃圾对象,解决了对象间循环引用的问题
  • 【缺点】:遍历所有对象,搜索效率不高

(该图片来源于网络)

在 Java 语言中,可作为 GC ROOTS 的对象有以下几种:

  • 虚拟机栈(栈帧中的本地变量表)中的引用对象
  • 方法区中类静态属性引用的对象
  • 方法区中类常量引用的对象
  • 本地方法栈中 JNI 引用的对象

2 垃圾回收算法

下面介绍几种垃圾收集算法的基本思想

2.1 标记-清除算法

  1. 标记:从 GC ROOTS 开始,遍历所有根对象,标记出所有需要回收的对象
  2. 清除:遍历堆中的所有对象,清除被标记的对象
  • 【优点】:没有产生额外的内存空间消耗
  • 【缺点】:标记和清除两个过程都需要遍历,效率低;标记清除后会产生大量不连续的内存碎片,如果需要分配较大对象时,无法找到足够的连续内存导致再次触发垃圾收集动作。

2.2 复制算法

将可用内存按容量划分为大小相等的两块,每次只使用其中一块。当这一块的内存用完了,就将还存活的对象复制到另外一块内存上,然后再把已使用过的内存空间一次清理掉。

  • 【优点】:实现简单,不产生内存碎片
  • 【缺点】:将内存缩小为原来的一半,代价太高

2.3 标记-整理算法

  1. 标记:标记过程和“标记-清除”算法的标记过程一样
  2. 整理:将所有存活的对象移动到另一端,严格按照内存地址次序依次排列活着的对象,然后直接清理掉端边界以外的内存
  • 【优点】:内存利用率高,不会产生内存碎片
  • 【缺点】:需要遍历对象,效率低

2.4 分代收集算法

一般把 Java 堆分为新生代和老年代,在新生代中,每次垃圾收集时发现有大批对象死去,只有少量存活,那就选用复制算法;而老年代中因为对象存活率高,没有额外空间对它进行分配担保,就使用“标记-清理”或者“标记-整理”算法来进行回收。

在新生代中按照 8:1:1 的比例分为 Eden 空间和两块小的 Survivor 空间,每次使用 Eden 和其中一块 Servivor。当回收时,将 Eden 和 Survivor 中还存活的对象一次性地复制到另外一块 Survivor 空间上,最后清理 Eden 和刚才用过的 Survivor 空间

  • 新生代 GC(Minor GC):指发生在新生代的垃圾收集动作,因为 Java 对象大多都具备朝生夕灭的特性,所以 Minor GC 非常频繁,一般回收速度也较快。触发时机是 Eden 空间分配满的时候。
  • 老年代 GC(Major GC / Full GC):指发生在老年代的 GC,出现了 Major GC,经常会伴随至少一次的 Minor GC(非绝对)。Major GC 的速度一般会比 Minor GC 慢 10 倍以上。

参考

《深入理解 Java 虚拟机(JVM高级特性与最佳实践) — 周志明》

坚持原创技术分享,您的支持将鼓励我继续创作!